
/******************************************************************************
 * MODULE     : concat_post.cpp
 * DESCRIPTION: Second pass for typesetting paragraphs
 *                - The real heights of brackets are determined
 *                - scripts are glued
 * COPYRIGHT  : (C) 1999  Joris van der Hoeven
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "analyze.hpp"
#include "concater.hpp"

using namespace moebius;

SI italic_correction (box, box);

/******************************************************************************
 * Miscellaneous routines
 ******************************************************************************/

int
concater_rep::prec (int i) {
  do
    i--;
  while ((i >= 0) &&
         (a[i]->type == OBSOLETE_ITEM || a[i]->type == MARKER_ITEM));
  return i;
}

int
concater_rep::succ (int i) {
  do
    i++;
  while ((i < N (a)) &&
         (a[i]->type == OBSOLETE_ITEM || a[i]->type == MARKER_ITEM));
  return i;
}

inline font
get_reference_font (box b, font fn) {
  // FIXME: the active font should really be part of 'line_item_rep'.
  // Currently, the font that is used for spacing parameters of scripts
  // can be completely unrelated to the fonts of the reference box and
  // the fonts of the scripts.  This occurs for instance when typesetting
  // (with "magnification" "10" [some box with limits]).  In such cases,
  // the env->fn at the time that we actually attach the limits is
  // typically ten times smaller than the font for the actual box.
  if (b->get_type () != TEXT_BOX) return fn;
  else return b->get_leaf_font ();
}

/******************************************************************************
 * Gluing scripts
 ******************************************************************************/

void
concater_rep::pre_glue () {
  int i= 0;
  while (true) {
    int j= succ (i);
    if (j >= N (a)) break;
    line_item item1= a[i];
    line_item item2= a[j];
    int       t1   = item1->type;
    int       t2   = item2->type;
    if (((t1 == RSUB_ITEM) && (t2 == RSUP_ITEM)) ||
        ((t1 == RSUP_ITEM) && (t2 == RSUB_ITEM)) ||
        ((t1 == LSUB_ITEM) && (t2 == LSUP_ITEM)) ||
        ((t1 == LSUP_ITEM) && (t2 == LSUB_ITEM))) {
      bool  flag1= (t1 == LSUB_ITEM) || (t1 == RSUB_ITEM);
      bool  flag2= (t1 == LSUB_ITEM) || (t1 == LSUP_ITEM);
      int   type = flag2 ? GLUE_LSUBS_ITEM : GLUE_RSUBS_ITEM;
      box   b1   = flag1 ? item1->b[0] : item2->b[0];
      box   b2   = flag1 ? item2->b[0] : item1->b[0];
      box   b    = script_box (b1->ip, b1, b2, env->fn);
      int   pen  = item2->penalty;
      space spc  = max (item1->spc, item2->spc);

      a[i]     = line_item (type, OP_SKIP, b, pen);
      a[i]->spc= spc;
      for (int k= i + 1; k < j; k++)
        if (a[k]->type == MARKER_ITEM)
          a[k]= line_item (OBSOLETE_ITEM, OP_SKIP, a[k]->b, a[k]->penalty);
      a[j]= line_item (OBSOLETE_ITEM, OP_SKIP, item2->b, pen);
    }
    i++;
  }
}

box
concater_rep::glue_left_markers (box b, int ref, int arg) {
  int i= arg + 1;
  while (i < ref && a[i]->type == OBSOLETE_ITEM)
    i++;
  if (i >= ref) return b;
  array<box> bs;
  array<SI>  spc;
  while (i < ref) {
    if (a[i]->type == MARKER_ITEM) {
      bs << a[i]->b;
      spc << 0;
      a[i]->type= OBSOLETE_ITEM;
    }
    i++;
  }
  bs << b;
  spc << 0;
  return concat_box (b->ip, bs, spc);
}

box
concater_rep::glue_right_markers (box b, int ref, int arg, bool flag) {
  int i= ref + 1;
  while (i < arg && a[i]->type == OBSOLETE_ITEM)
    i++;
  if (i >= arg) return b;
  array<box> bs;
  array<SI>  spc;
  if (flag) {
    for (int j= 0; j < N (b); j++) {
      bs << b[j];
      spc << 0;
    }
  }
  else {
    bs << b;
    spc << 0;
  }
  while (i < arg) {
    if (a[i]->type == MARKER_ITEM) {
      bs << a[i]->b;
      spc << 0;
      a[i]->type= OBSOLETE_ITEM;
    }
    i++;
  }
  return concat_box (b->ip, bs, spc);
}

void
concater_rep::glue (box b, int ref, int arg) {
  if (a[ref]->op_type == OP_BIG && arg >= ref && !a[ref]->limits) {
    font ref_fn= get_reference_font (a[ref]->b, env->fn);
    if (ref_fn->math_type != MATH_TYPE_NORMAL)
      if (a[ref]->spc->def > 0) {
        space spc= ref_fn->spc;
        a[ref]->spc+= space (spc->min / 3, spc->def / 3, spc->def / 3);
      }
  }

  space spc= max (a[ref]->spc, a[arg]->spc);

  a[arg]= line_item (OBSOLETE_ITEM, OP_SKIP, a[arg]->b, a[arg]->penalty);
  a[ref]=
      line_item (arg < ref ? GLUE_LEFT_ITEM : GLUE_RIGHT_ITEM, a[ref]->op_type,
                 b, min (a[ref]->penalty, a[arg]->penalty));
  a[ref]->spc= spc;
}

void
concater_rep::glue (box b, int ref, int arg1, int arg2) {
  if (a[ref]->op_type == OP_BIG && !a[ref]->limits) {
    font ref_fn= get_reference_font (a[ref]->b, env->fn);
    if (ref_fn->math_type != MATH_TYPE_NORMAL)
      if (a[ref]->spc->def > 0) {
        space spc= ref_fn->spc;
        a[ref]->spc+= space (spc->min / 3, spc->def / 3, spc->def / 3);
      }
  }

  space spc= max (a[ref]->spc, max (a[arg1]->spc, a[arg2]->spc));
  int   pen= min (a[ref]->penalty, min (a[arg1]->penalty, a[arg2]->penalty));

  space ref_spc= a[ref]->spc;
  a[arg1]    = line_item (OBSOLETE_ITEM, OP_SKIP, a[arg1]->b, a[arg1]->penalty);
  a[arg2]    = line_item (OBSOLETE_ITEM, OP_SKIP, a[arg2]->b, a[arg2]->penalty);
  a[ref]     = line_item (GLUE_BOTH_ITEM, a[ref]->op_type, b, pen);
  a[ref]->spc= spc;
}

void
concater_rep::handle_scripts (int start, int end) {
  int i;
  for (i= start; i <= end;) {
    if ((a[i]->type == OBSOLETE_ITEM) || (a[i]->type == LSUB_ITEM) ||
        (a[i]->type == LSUP_ITEM) || (a[i]->type == GLUE_LSUBS_ITEM) ||
        (a[i]->type == RSUB_ITEM) || (a[i]->type == RSUP_ITEM) ||
        (a[i]->type == GLUE_RSUBS_ITEM) ||
        (a[i]->type == CONTROL_ITEM && L (a[i]->t) == DATOMS)) {
      i++;
      continue;
    }

    path sip;
    int  l= prec (i);
    box  lb1, lb2;
    if (l < start) l= -1;
    else switch (a[l]->type) {
      case LSUB_ITEM:
        lb1= a[l]->b[0];
        sip= lb1->ip;
        break;
      case LSUP_ITEM:
        lb2= a[l]->b[0];
        sip= lb2->ip;
        break;
      case GLUE_LSUBS_ITEM:
        lb1= a[l]->b[0];
        lb2= a[l]->b[1];
        sip= lb2->ip;
        break;
      default:
        l= -1;
      }

    int r= succ (i);
    box rb1, rb2;
    if (r > end) r= N (a);
    else switch (a[r]->type) {
      case RSUB_ITEM:
        rb1= a[r]->b[0];
        sip= rb1->ip;
        break;
      case RSUP_ITEM:
        rb2= a[r]->b[0];
        sip= rb2->ip;
        break;
      case GLUE_RSUBS_ITEM:
        rb1= a[r]->b[0];
        rb2= a[r]->b[1];
        sip= rb2->ip;
        break;
      default:
        r= N (a);
      }

    box b;
    if (l == -1) {
      if (r == N (a)) {
        i++;
        continue;
      }
      else {
        font ref_fn= get_reference_font (a[i]->b, env->fn);
        box  mb    = glue_right_markers (a[i]->b, i, r, false);
        if (a[i]->limits) b= limit_box (sip, mb, rb1, rb2, ref_fn, true);
        else b= right_script_box (sip, mb, rb1, rb2, ref_fn, env->vert_pos);
        glue (b, i, r);
      }
    }
    else {
      font ref_fn= get_reference_font (a[i]->b, env->fn);
      box  mb    = glue_left_markers (a[i]->b, i, l);
      if (r == N (a)) {
        b= left_script_box (sip, mb, lb1, lb2, ref_fn, env->vert_pos);
        glue (b, i, l);
      }
      else {
        mb= glue_right_markers (mb, i, r, true);
        b = side_box (sip, mb, lb1, lb2, rb1, rb2, ref_fn, env->vert_pos);
        glue (b, i, l, r);
      }
    }
  }
}

void
concater_rep::clean_and_correct () {
  array<line_item> new_a;
  int              i, prev= -1;
  for (i= 0; i < N (a); i++)
    if (a[i]->type != OBSOLETE_ITEM) {
      if (a[i]->b->w () != 0) {
        if (prev != -1) {
          SI cor= ::italic_correction (a[prev]->b, a[i]->b);
          if (cor != 0) {
            if (a[prev]->b->right_correction () != 0)
              a[prev]->spc+= space (cor);
            else a[i - 1]->spc+= space (cor);
          }
        }
        prev= i;
      }
      new_a << a[i];
    }
  a= new_a;
}

/******************************************************************************
 * Resize brackets
 ******************************************************************************/

void
concater_rep::handle_matching (int start, int end) {
  // cout << "matching " << start << " -- " << end << "\n";
  // cout << a << "\n\n";
  int  i;
  SI   y1    = MAX_SI;
  SI   y2    = -MAX_SI;
  bool uninit= true;
  a[start]->penalty++;
  a[end]->penalty++;
  for (i= start + 1; i < end; i++) {
    if (a[i]->type == OBSOLETE_ITEM) continue;
    // cout << "  " << a[i] << ": " << (a[i]->b->y2- a[i]->b->y1) << "\n";
    // y1= min (y1, a[i]->b->sub_base());
    // y2= max (y2, a[i]->b->sup_base());
    SI lo, hi;
    a[i]->b->get_bracket_extents (lo, hi);
    y1= min (y1, lo);
    y2= max (y2, hi);
    a[i]->penalty++;
    uninit= false;
  }
  if (uninit) {
    y1= min (a[start]->b->y1, a[end]->b->y2);
    y2= max (a[start]->b->y1, a[end]->b->y2);
  }

  for (i= start; i <= end; i++) {
    int tp= a[i]->type;
    if (tp == LEFT_BRACKET_ITEM || tp == MIDDLE_BRACKET_ITEM ||
        tp == RIGHT_BRACKET_ITEM) {
      string ls= a[i]->b->get_leaf_string ();
      pencil lp= a[i]->b->get_leaf_pencil ();
      font   fn= a[i]->b->get_leaf_font ();

      // find the middle of the bracket, around where to center
      SI   mid   = (a[i]->b->y1 + a[i]->b->y2) >> 1;
      bool custom= N (ls) > 2 && is_digit (ls[N (ls) - 2]) && !ends (ls, "-0>");
      if (custom) {
        int pos= N (ls) - 1;
        while (pos > 0 && ls[pos] != '-')
          pos--;
        if (pos > 0 && ls[pos - 1] == '-') pos--;
        string ss  = ls (0, pos) * ">";
        box    auxb= text_box (a[i]->b->ip, 0, ss, fn, lp);
        mid        = (auxb->y1 + auxb->y2) >> 1;
      }

      // make symmetric and prevent from too large delimiters if possible
      SI Y1   = y1 + (fn->sep >> 1);
      SI Y2   = y2 - (fn->sep >> 1);
      SI tol  = fn->sep << 1;
      SI drift= ((Y1 + Y2) >> 1) - mid; // fn->yfrac;
      if (drift < 0) Y2+= min (-drift, tol) << 1;
      else Y1-= min (drift, tol) << 1;

      // further adjustments when the enclosed expression is not very high
      // and for empty brackets
      SI h= y2 - y1 - fn->sep;
      SI d= 5 * fn->yx - h;
      if (d > 0) {
        Y1+= d / 12;
        Y2-= d / 12;
      }
      if (N (ls) >= 8 && (ls[6] == '.' || ls[7] == '.'))
        if (starts (ls, "<left-.") || starts (ls, "<right-.")) {
          Y1+= d / 6;
          Y2-= d / 12;
        }

      // replace item by large or small delimiter
      if (Y1 < fn->y1 || Y2 > fn->y2 || custom || use_poor_rubber (fn))
        a[i]->b= delimiter_box (a[i]->b->ip, ls, fn, lp, Y1, Y2, mid, y1, y2);
      else {
        string s= "<nobracket>";
        int    j;
        for (j= 0; j < N (ls); j++)
          if (ls[j] == '-') break;
        if (j < N (ls) && ls[N (ls) - 1] == '>') s= ls (j + 1, N (ls) - 1);
        if (N (s) > 1 && s[0] != '<') s= "<" * s * ">";
        else if (N (s) == 0 || s == ".") s= "<nobracket>";
        a[i]->b= text_box (a[i]->b->ip, 0, s, fn, lp);
        tp     = STD_ITEM;
      }
      a[i]->type= STD_ITEM;
    }
    if (tp == LEFT_BRACKET_ITEM)
      for (int j= i - 1; j >= 0; j--) {
        if (a[j]->type == MARKER_ITEM) {
          SI Y1= a[i]->b->y1;
          SI Y2= a[i]->b->y2;
          // a[j]->b = marker_box (a[j]->b->find_lip (), 0, Y1, 0, Y2, a[j]->b);
          a[j]->b   = marker_box (a[j]->b->find_lip (), 0, Y1, 0, Y2, a[i]->b);
          a[j]->type= STD_ITEM;
        }
        else if (a[j]->type != CONTROL_ITEM) break;
      }
    if (tp == RIGHT_BRACKET_ITEM)
      for (int j= i + 1; j < N (a); j++) {
        if (a[j]->type == MARKER_ITEM) {
          SI Y1= a[i]->b->y1;
          SI Y2= a[i]->b->y2;
          // a[j]->b = marker_box (a[j]->b->find_lip (), 0, Y1, 0, Y2, a[j]->b);
          a[j]->b   = marker_box (a[j]->b->find_lip (), 0, Y1, 0, Y2, a[i]->b);
          a[j]->type= STD_ITEM;
        }
        else if (a[j]->type != CONTROL_ITEM) break;
      }
  }
}

void
concater_rep::handle_brackets () {
  int first= -1, start= 0, i= 0;
  while (i < N (a)) {
    if (a[i]->type == LEFT_BRACKET_ITEM) {
      if (first == -1) first= i;
      start= i;
    }
    if (a[i]->type == RIGHT_BRACKET_ITEM) {
      handle_scripts (succ (start), prec (i));
      handle_matching (start, i);
      if (first != -1) i= first - 1;
      start= 0;
      first= -1;
    }
    i++;
  }
  if (N (a) > 0) {
    handle_scripts (0, N (a) - 1);
    handle_matching (0, N (a) - 1);
  }
}

/******************************************************************************
 * Kill invalid spaces
 ******************************************************************************/

void
concater_rep::kill_spaces () {
  int i;
  for (i= N (a) - 1; (i > 0) && (a[i]->type == CONTROL_ITEM); i--)
    a[i - 1]->spc= space (0);
  for (i= 0; (i < N (a)) && (a[i]->type == CONTROL_ITEM); i++)
    a[i]->spc= space (0);

  for (i= 0; i < N (a); i++)
    if (a[i]->type == CONTROL_ITEM) {
      if (is_formatting (a[i]->t)) {
        tree_label lab= L (a[i]->t);
        if ((lab == NEXT_LINE) || (lab == LINE_BREAK) || (lab == NEW_LINE)) {
          if (i > 0) a[i - 1]->spc= space (0);
          a[i]->spc= space (0);
        }
      }

      if (is_tuple (a[i]->t, "env_par") || is_tuple (a[i]->t, "env_page"))
        a[i]->spc= space (0);
    }
}

/******************************************************************************
 * Main control
 ******************************************************************************/

void
concater_rep::finish () {
  kill_spaces ();
  pre_glue ();
  handle_brackets ();
  clean_and_correct ();
}
